/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package chat;

import chat.room.ChatRoom;
import chat.room.IChatRoomEventListener;
import dalfactory.SDALFactory;
import fsktms.IMessage;
import fsktms.messagemanager.IMessageListener;
import fsktms.server.NetServer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.rmi.RemoteException;
import java.util.*;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author salm
 */
public class ChatServer implements IMessageListener, IChatRoomEventListener
{
    private NetServer msgSender;
    private final Map<String, String> accountAddresses = new HashMap();
    private final Map<String, ChatRoom> rooms = new HashMap();
    
    public ChatServer(NetServer msgSender)
    {
        this.msgSender = msgSender;
    }

    @Override
    public void onHasNewMessage(final IMessage msg)
    {
        (new Thread()
        {
            @Override
            public void run()
            {
                processMessage(msg);
            }
        }).start();
    }
    
    public void registerAccount(String chatID, String address)
    {
        synchronized (this.accountAddresses)
        {
            this.accountAddresses.put(chatID, address);
        }
    }
    
    public void leaveAccount(String address)
    {
        synchronized (this.accountAddresses)
        {
            String chatID = null;
            for (Map.Entry<String, String> acc : this.accountAddresses.entrySet())
            {
                if (acc.getValue().equals(address))
                {
                    chatID = acc.getKey();
                    break;
                }
            }

            if (chatID != null)
            {
                this.accountAddresses.remove(chatID);
                synchronized (this.rooms)
                {
                    for (Entry<String, ChatRoom> room : this.rooms.entrySet())
                    {
                        room.getValue().removeUser(chatID);
                    }
                }
            }
        }
    }
    
    private void processMessage(IMessage msg)
    {
        try (ObjectInputStream oIS = new ObjectInputStream(new ByteArrayInputStream(msg.getData())))
        {
            ChatMessage chatMsg = (ChatMessage) oIS.readObject();
            if (chatMsg != null)
            {
                switch (chatMsg.getMessageType())
                {
                    case ChatMessage.CHATMESSAGE_TYPE_TEXT:
                        this.processTextMessage((TextChatMessage) chatMsg, msg);
                        break;
                    case ChatMessage.CHATMESSAGE_TYPE_SOUND:
                        this.processSoundMessage((SoundChatMessage) chatMsg, msg);
                        break;
                    case ChatMessage.CHATMESSAGE_TYPE_COMMAND:
                        this.processCommandMessage((CommandMessage) chatMsg, msg);
                    default:
                        break;
                }
            }
        } catch (IOException | ClassNotFoundException ex) {
            Logger.getLogger(ChatServer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    private void processTextMessage(TextChatMessage textMsg, IMessage msg)
    {
        byte []dataMsg = msg.getData();
        String receiveID = textMsg.getReceiverID();
        if (this.rooms.containsKey(receiveID))
        {
            Iterable<String> roomUsers = this.rooms.get(receiveID).getUsers();
            for (String userID : roomUsers)
            {
                this.sendMessageToUser(userID, dataMsg);
            }
        }
        else if (this.accountAddresses.containsKey(receiveID))
        {
            this.sendMessageToUser(receiveID, dataMsg);
        }
        
        try {
            // save message to database
            SDALFactory.getInstance().getMessage().addNewMessage(textMsg);
        } catch (RemoteException ex) {
            Logger.getLogger(ChatServer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void processSoundMessage(SoundChatMessage soundChatMessage, IMessage msg) {
        byte []dataMsg = msg.getData();
        String receiveID = soundChatMessage.getReceiverID();
        if (this.rooms.containsKey(receiveID))
        {
            Iterable<String> roomUsers = this.rooms.get(receiveID).getListenUsers();
            for (String userID : roomUsers)
            {
                this.sendMessageToUser(userID, dataMsg);
            }
        }
        else if (this.accountAddresses.containsKey(receiveID))
        {
            this.sendMessageToUser(receiveID, dataMsg);
        }
    }

    private void processCommandMessage(CommandMessage commandMessage, IMessage msg)
    {
        String userID = commandMessage.getSenderID();
        switch (commandMessage.getCommandType())
        {
            case CommandMessage.COMMANDMESSAGE_TYPE_USER_JOINED:
            case CommandMessage.COMMANDMESSAGE_TYPE_USER_CHANGED:
                this.registerAccount(userID, msg.getHostAddress());
                break;
            case CommandMessage.COMMANDMESSAGE_TYPE_USER_LEFT:
                this.leaveAccount(userID);
                break;
        }
    }
    
    private void sendMessageToUser(String userID, byte[] msgData)
    {
        String address = this.accountAddresses.get(userID);
        if (address != null)
        {
                this.msgSender.sendMessage(address, msgData, ChatMessage.CHAT_MESSAGE_MAGICKEY);
        }
    }
    
    public Collection<String> getRoomIDs()
    {
        return this.rooms.keySet();
    }

    /**
     * @return the roomIDs
     */
    public Iterable<ChatRoom> getRooms() {
        return this.rooms.values();
    }
    
    public void addRoom(ChatRoom room)
    {
        this.rooms.put(room.getRoomID(), room);
        room.getEventListeners().add(this);
    }
    
    public void removeRoom(String roomID)
    {
        this.rooms.remove(roomID);
    }
    
    public ChatRoom getRoom(String roomID)
    {
        return this.rooms.get(roomID);
    }

    @Override
    public void userJoined(ChatRoom room, String userID) {
        Iterable<String> roomUsers = room.getUsers();
        for (String user : roomUsers)
        {
            CommandMessage commandMsg = new CommandMessage(room.getRoomID(), user,
                    CommandMessage.COMMANDMESSAGE_TYPE_USER_JOINED, userID);
            this.sendMessageToUser(user, this.makeByteArrayForMessage(commandMsg));
        }
    }

    @Override
    public void userLeft(ChatRoom room, String userID) {
        Iterable<String> roomUsers = room.getUsers();
        for (String user : roomUsers)
        {
            CommandMessage commandMsg = new CommandMessage(room.getRoomID(), user,
                    CommandMessage.COMMANDMESSAGE_TYPE_USER_LEFT, userID);
            this.sendMessageToUser(user, this.makeByteArrayForMessage(commandMsg));
        }
    }

    @Override
    public void userChanged(ChatRoom room) {
        Iterable<String> roomUsers = room.getUsers();
        for (String user : roomUsers)
        {
            CommandMessage commandMsg = new CommandMessage(room.getRoomID(), user,
                    CommandMessage.COMMANDMESSAGE_TYPE_USER_CHANGED, null);
            this.sendMessageToUser(user, this.makeByteArrayForMessage(commandMsg));
        }
    }

    @Override
    public void talkUserChanged(ChatRoom room, String talkUser)
    {
        CommandMessage commandMsg = new CommandMessage(room.getRoomID(), talkUser,
                CommandMessage.COMMANDMESSAGE_TYPE_BECOME_TALKER, null);
        this.sendMessageToUser(talkUser, this.makeByteArrayForMessage(commandMsg));
    }
    
    private byte[] makeByteArrayForMessage(ChatMessage chatMsg)
    {
        try (ByteArrayOutputStream bOS = new ByteArrayOutputStream())
        {
            ObjectOutputStream oOs = new ObjectOutputStream(bOS);
            oOs.writeObject(chatMsg);
            
            return bOS.toByteArray();
        }
        catch (IOException ex)
        {
            
        }
        
        return null;
    }
}
